/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.reviewerplugin.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.pearreview.reviewerplugin.Report;
import com.pearreview.util.review.Aspect;
import com.pearreview.util.review.Finding;
import com.pearreview.util.review.IdLinkedList;
import com.pearreview.util.review.Product;
import com.pearreview.util.review.Severity;
import com.pearreview.util.review.io.NotValidXMLException;
import com.pearreview.util.review.io.XMLSchemaValidator;

public class XMLReportReader implements ReportReader, Serializable {
	
	private static final long serialVersionUID = 255521612423416813L;
	
	public static final URL SCHEMA_URL = 
			XMLReportReader.class.getResource("schema.xsd");
	
	private DatatypeFactory dtFactory;
	
	protected XMLSchemaValidator getValidator() throws SAXException {
		return new XMLSchemaValidator(SCHEMA_URL);
	}
	
	protected DocumentBuilder getDocumentBuilder() throws 
			ParserConfigurationException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setIgnoringComments(true);
		factory.setNamespaceAware(false);
		return factory.newDocumentBuilder();
	}

	@Override
	public Report read(File file) throws SAXException, IOException, 
			NotValidXMLException, ParserConfigurationException, 
			DatatypeConfigurationException {
		
		//throw an exception if file is not valid
		XMLSchemaValidator validator = getValidator();
		if (!validator.validate(file)) {
			throw new NotValidXMLException(validator.getLastErrorMessage());
		}

		Document document = getDocumentBuilder().parse(file);
		
		return read(document);
	}
	
	@Override
	public Report read(InputStream is) throws SAXException, IOException, 
			NotValidXMLException, ParserConfigurationException, 
			DatatypeConfigurationException {
		//throw an exception if file is not valid
		XMLSchemaValidator validator = getValidator();
		if (!validator.validate(is)) {
			throw new NotValidXMLException(validator.getLastErrorMessage());
		}

		Document document = getDocumentBuilder().parse(is);
		
		return read(document);
	}
	
	protected Report read(Document document) throws 
			DatatypeConfigurationException {
		dtFactory = DatatypeFactory.newInstance();
		
		Element root = document.getDocumentElement();
		
		Report report = null;
		
		if(root.getNodeName().endsWith("report")) {
			report = readReport(root);
		}
		
		return report;
	}
	
	protected Report readReport(Element reportRoot) {
		Report report = new Report();
		
		NodeList nodes = reportRoot.getChildNodes();
		for( int i = 0 ; i < nodes.getLength() ; i++ ) {
			Node node = nodes.item(i);
			
			if(node.getNodeName().endsWith("name")) {
				report.setName(node.getTextContent());
			} else if(node.getNodeName().endsWith("description")) {
				report.setDescription(node.getTextContent());
			} else if(node.getNodeName().endsWith("product")) {
				report.setProduct(readProduct(node));
			} else if(node.getNodeName().endsWith("severities")) {
				report.setSeverities(readSeverities(node));
			} else if(node.getNodeName().endsWith("impression")) {
				report.setImpression(node.getTextContent());
			} else if(node.getNodeName().endsWith("recommendation")) {
				report.setRecommendation(node.getTextContent());
			} else if(node.getNodeName().endsWith("comments")) {
				report.setComments(node.getTextContent());
			} else if(node.getNodeName().endsWith("finding")) {
				if(report.getFindings() == null) {
					report.setFindings(new IdLinkedList<Finding>());
				}
				report.getFindings().add(readFinding(node));
			}  else if(node.getNodeName().endsWith("aspect")) {
				if(report.getAspects() == null) {
					report.setAspects(new IdLinkedList<Aspect>());
				}
				report.getAspects().add(readAspect(node));
			} else if(node.getNodeName().endsWith("preparation-time") 
					&& dtFactory != null) {
				Duration pTime = dtFactory.newDuration(node.getTextContent());
				report.setPreparationTime(pTime.getTimeInMillis(
						new GregorianCalendar(1970, 1, 1)));
			}
		}
		
		return report;
	}
		
	protected Product readProduct(Node productRoot) {
		Product product = new Product();
		
		NodeList nodes = productRoot.getChildNodes();
		for( int i = 0 ; i < nodes.getLength() ; i++ ) {
			Node node = nodes.item(i);
			
			if(node.getNodeName().endsWith("name")) {
				product.setName(node.getTextContent());
			} else if(node.getNodeName().endsWith("reference")) {
				if(product.getReference() == null) {
					product.setReference(new LinkedList<String>());
				}
				product.getReference().add(node.getTextContent());
			} else if(node.getNodeName().endsWith("version")) {
				product.setVersion(node.getTextContent());
			}
		}
		
		return product;
	}
	
	protected List<Severity> readSeverities(Node severitiesRoot) {
		NodeList nodes = severitiesRoot.getChildNodes();
		List<Severity> severities = new ArrayList<Severity>(nodes.getLength());
		
		for( int i = 0 ; i < nodes.getLength() ; i++ ) {
			Node node = nodes.item(i);
			
			if(node.getNodeName().endsWith("severity")) {
				severities.add(new Severity(node.getTextContent()));
			}
		}
		
		return severities;
	}
	
	protected Finding readFinding(Node findingRoot) {
		Finding finding = new Finding();
		
		NodeList nodes = findingRoot.getChildNodes();
		
		for( int i = 0 ; i < nodes.getLength() ; i++ ) {
			Node node = nodes.item(i);
			
			if(node.getNodeName().endsWith("id")) {
				finding.setId(node.getTextContent());
			} else if(node.getNodeName().endsWith("description")) {
				finding.setDescription(node.getTextContent());
			} else if(node.getNodeName().endsWith("severity")) {
				finding.setSeverity(new Severity(node.getTextContent()));
			} else if(node.getNodeName().endsWith("aspect")) {
				if(finding.getAspects() == null) {
					finding.setAspects(new LinkedList<String>());
				}
				finding.getAspects().add(node.getTextContent());
			} else if(node.getNodeName().endsWith("external-reference")) {
				if(finding.getExternalReferences() == null) {
					finding.setExternalReferences(new LinkedList<URI>());
				}
				try {
					URI uri = new URI(node.getTextContent());
					finding.getExternalReferences().add(uri);
				} catch(URISyntaxException e) {
					// was checked by validator, never throws
				}
			} else if(node.getNodeName().endsWith("reference")) {
				if(finding.getReferences() == null) {
					finding.setReferences(new LinkedList<String>());
				}
				finding.getReferences().add(node.getTextContent());
			}
		}
		
		return finding;
	}
	
	protected Aspect readAspect(Node aspectRoot) {
		Aspect aspect = new Aspect();
		
		NodeList nodes = aspectRoot.getChildNodes();
		
		for( int i = 0 ; i < nodes.getLength() ; i++ ) {
			Node node = nodes.item(i);
			
			if(node.getNodeName().endsWith("id")) {
				aspect.setId(node.getTextContent());
			} else if(node.getNodeName().endsWith("category")) {
				aspect.setCategory(node.getTextContent());
			} else if(node.getNodeName().endsWith("directive")) {
				aspect.setDirective(node.getTextContent());
			} else if(node.getNodeName().endsWith("description")) {
				aspect.setDescription(node.getTextContent());
			}
		}
		
		return aspect;
	}
}